/// Framework Core Public-Key Cryptography with secp256r1/NISTP-256 ECC Curves
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.crypt.ecc256r1;

{
  *****************************************************************************

   High-Performance secp256r1/NISTP-256/prime256v1 Elliptic-Curve Cryptography
    - Low-Level ECC secp256r1 ECDSA and ECDH Functions
    - Middle-Level Certificate-based Public Key Cryptography

   If mormot.crypt.openssl.RegisterOpenSsl is called, uses faster OpenSSL.

  *****************************************************************************

}

interface

{$I ..\mormot.defines.inc}

uses
  classes,
  sysutils,
  mormot.core.base,
  mormot.core.os,
  mormot.core.rtti,
  mormot.core.unicode,
  mormot.core.text,
  mormot.core.buffers, // for Base64 and baudot encoding
  mormot.core.datetime,
  mormot.crypt.core;



{ ***************** Low-Level ECC secp256r1 ECDSA and ECDH Functions }

const
  /// the size of the 256-bit memory structure used for secp256r1
  // - map 32 bytes of memory
  ECC_BYTES = SizeOf(THash256);

  /// Mon, 01 Aug 2016 encoded as COM/TDateTime value
  // - used to compute TEccDate 16-bit values to/from a TDateTime
  // - 16-bit day resolution allow values from year 2016 to 2196
  ECC_DELTA = 42583;


type
  /// store a public key for ECC secp256r1 cryptography
  // - use Ecc256r1MakeKey() to generate such a key
  // - stored in compressed form with its standard byte header, i.e. each
  // public key consumes 33 bytes of memory
  TEccPublicKey = array[0..ECC_BYTES] of byte;

  /// store a public key for ECC secp256r1 cryptography
  // - use Ecc256r1Uncompress() to compute such a key from a TEccPublicKey
  // - stored in uncompressed form, consuming 64 bytes of memory
  TEccPublicKeyUncompressed = array[0..(ECC_BYTES * 2) - 1] of byte;

  /// store a private key for ECC secp256r1 cryptography
  // - use Ecc256r1MakeKey() to generate such a key
  // - stored in compressed form, i.e. each private key consumes 32 bytes of memory
  TEccPrivateKey = array[0..ECC_BYTES - 1] of byte;

  /// store a public key and a private key for ECC secp256r1 cryptography
  // - used e.g. for ECDHE shared secret computation, or to store a full key
  TEccKeyPair = packed record
    /// a public key for ECC secp256r1 cryptography
    pub: TEccPublicKey;
    /// a private key for ECC secp256r1 cryptography
    priv: TEccPrivateKey;
  end;

  /// store a 256-bit hash, as expected by ECC secp256r1 cryptography
  // - see e.g. Ecc256r1Sign() and Ecc256r1Verify() functions
  TEccHash = THash256;

  /// store a signature, as generated by ECC secp256r1 cryptography
  // - see e.g. Ecc256r1Sign() and Ecc256r1Verify() functions
  // - contains ECDSA's R and S integers
  // - each ECC signature consumes 64 bytes of memory
  TEccSignature = array[0..(ECC_BYTES * 2) - 1] of byte;

  /// store an encryption key, as generated by ECC secp256r1 cryptography
  // - use Ecc256r1SharedSecret() to compute such a key from public/private keys
  // - 256-bit / 32 bytes derivation from secp256r1 ECDH is expected to have at
  // least 247-bit of entropy so could better be derivated via a KDF before used
  // as encryption secret - see @http://crypto.stackexchange.com/a/9428/40200
  TEccSecretKey = THash256;

  PEccPublicKey = ^TEccPublicKey;
  PEccPublicKeyUncompressed = ^TEccPublicKeyUncompressed;
  PEccPrivateKey = ^TEccPrivateKey;
  PEccHash = ^TEccHash;
  PEccSignature = ^TEccSignature;
  PEccSecretKey = ^TEccSecretKey;
  PEccKeyPair = ^TEccKeyPair;

var
  /// create a public/private key pair
  // - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
  // - direct low-level access to the our pascal version, or OpenSSL wrappers
  // - returns true if the key pair was generated successfully in pub/priv
  // - returns false if an error occurred
  // - this function is thread-safe and does not perform any memory allocation
  Ecc256r1MakeKey: function(out pub: TEccPublicKey; out priv: TEccPrivateKey): boolean;

  /// compute a shared secret given your secret key and someone else's public key
  // - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
  // - direct low-level access to the our pascal version, or OpenSSL wrappers
  // - note: it is recommended that you hash the result of Ecc256r1SharedSecret
  // before using it for symmetric encryption or HMAC (via an intermediate KDF)
  // - returns true if the shared secret was generated successfully in secret
  // - returns false if an error occurred
  // - this function is thread-safe and does not perform any memory allocation
  Ecc256r1SharedSecret: function(const pub: TEccPublicKey; const priv: TEccPrivateKey;
    out secret: TEccSecretKey): boolean;

  /// generate an ECDSA signature for a given hash value
  // - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
  // - direct low-level access to the our pascal version, or OpenSSL wrappers
  // - returns true if the signature was successfully generated in sign
  // - returns false if an error occurred
  // - this function is thread-safe and does not perform any memory allocation
  Ecc256r1Sign: function(const priv: TEccPrivateKey; const hash: TEccHash;
    out sign: TEccSignature): boolean;

  /// verify an ECDSA signature
  // - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
  // - direct low-level access to the our pascal version, or OpenSSL wrappers
  // - returns true if the supplied signature is valid
  // - returns false if an error occurred
  // - this function is thread-safe and does not perform any memory allocation
  Ecc256r1Verify: function(const pub: TEccPublicKey; const hash: TEccHash;
    const sign: TEccSignature): boolean;

  /// decompress a secp256r1 curve public key
  // - could be used before calling Ecc256r1VerifyUncomp()
  // - direct low-level access to the our pascal version - no OpenSSL yet
  Ecc256r1Uncompress: procedure(const Compressed: TEccPublicKey;
    out Uncompressed: TEccPublicKeyUncompressed);

  /// verify an ECDSA signature using an uncompressed public supplied
  // - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
  // - direct low-level access to the our pascal version - no OpenSSL yet
  // - returns true if the supplied signature is valid
  // - returns false if an error occurred
  // - this function is thread-safe and does not perform any memory allocation
  // - it is slightly faster than plain Ecc256r1Verify() using TEccPublicKey,
  // with the pascal version since public key doesn't need to be uncompressed,
  // but it is slower when using the OpenSSL backend
  Ecc256r1VerifyUncomp: function(const PublicKey: TEccPublicKeyUncompressed;
    const Hash: TEccHash; const Signature: TEccSignature): boolean;

/// compress a public key for ECC secp256r1 cryptography
// - convert its uncompressed/flat form (64 bytes of memory) into its compressed
// form with its standard byte header (33 bytes of memory)
// - a pascal version is good enough for this immediate bit copy operation
procedure Ecc256r1Compress(const Uncompressed: TEccPublicKeyUncompressed;
  out Compressed: TEccPublicKey);

/// derivate an ECC secp256r1 cryptography public key from a private key
procedure Ecc256r1PublicFromPrivate(const PrivateKey: TEccPrivateKey;
  out PublicKey: TEccPublicKey);

/// check if an ECC secp256r1 cryptography public key does match a private key
function Ecc256r1MatchKeys(const PrivateKey: TEccPrivateKey;
  const PublicKey: TEccPublicKey): boolean;

/// decode an uncompressed ASN-1 public key for ECC secp256r1 cryptography
// - input is likely to have a $04 initial byte for ASN-1 uncompressed key,
// as stored in a X.509 Certificate SubjectPublicKey field
function Ecc256r1ExtractAsn1(const Asn1: RawByteString;
  out Uncompressed: TEccPublicKeyUncompressed): boolean;

/// compress an uncompressed ASN-1 public key for ECC secp256r1 cryptography
// - input is likely to have a $04 initial byte for ASN-1 uncompressed key,
// as stored in a X.509 Certificate SubjectPublicKey field
function Ecc256r1CompressAsn1(const Uncompressed: RawByteString;
  out Compressed: TEccPublicKey): boolean;

/// save an ECC secp256r1 cryptography public key as expected by ASN-1
// - include a $04 initial byte as uncompressed key, and change endianness
// - as stored in a X.509 Certificate SubjectPublicKey field
function Ecc256r1UncompressAsn1(const Compressed: TEccPublicKey): RawByteString;

/// just a wrapper around Ecc256r1Verify/Ecc256r1VerifyUncomp depending on unc
// - this unit is faster with uncompressed keys, whereas OpenSSL prefers to
// work with compressed keys
function Ecc256r1DoVerify(const pub: TEccPublicKey; unc: PEccPublicKeyUncompressed;
  const hash: TEccHash; const sign: TEccSignature): boolean;


/// pascal function to create a secp256r1 public/private key pair
function ecc_make_key_pas(out PublicKey: TEccPublicKey;
  out PrivateKey: TEccPrivateKey): boolean;

/// pascal function to compute a secp256r1 shared secret given your secret key
// and someone else's public key (in compressed format)
function ecdh_shared_secret_pas(const PublicKey: TEccPublicKey;
  const PrivateKey: TEccPrivateKey; out Secret: TEccSecretKey): boolean;

/// pascal function to compute a secp256r1 shared secret given your secret key
// and someone else's public key (in uncompressed/flat format)
// - this overloaded function is slightly faster than the one using TEccPublicKey,
// since public key doesn't need to be uncompressed
function ecdh_shared_secret_uncompressed_pas(const PublicPoint: TEccPublicKeyUncompressed;
  const PrivateKey: TEccPrivateKey; out Secret: TEccSecretKey): boolean;

/// pascal function to generate an ECDSA secp256r1 signature for a given hash value
function ecdsa_sign_pas(const PrivateKey: TEccPrivateKey; const Hash: TEccHash;
  out Signature: TEccSignature): boolean;

/// pascal function to verify an ECDSA secp256r1 signature from someone else's
// public key (in compressed format)
function ecdsa_verify_pas(const PublicKey: TEccPublicKey; const Hash: TEccHash;
  const Signature: TEccSignature): boolean;

/// pascal function to verify an ECDSA secp256r1 signature from someone else's
// public key (in uncompressed/flat format)
// - this overloaded function is slightly faster than the one using TEccPublicKey,
// since public key doesn't need to be uncompressed
function ecdsa_verify_uncompressed_pas(const PublicKey: TEccPublicKeyUncompressed;
  const Hash: TEccHash; const Signature: TEccSignature): boolean;

/// uncompress a public key for ECC secp256r1 cryptography
// - convert from its compressed form with its standard byte header
// (33 bytes of memory) into uncompressed/flat form (64 bytes of memory)
procedure ecc_uncompress_key_pas(const Compressed: TEccPublicKey;
  out Uncompressed: TEccPublicKeyUncompressed);


type
  /// verification of a ECDSA signature using ECC secp256r1 cryptography
  // - this class encapsultate the public key storage in the native form of
  // the cryptographic library, which may be this unit pascal or OpenSSL
  // - it is therefore slightly faster than Ecc256r1Verify()
  TEcc256r1VerifyAbstract = class
  protected
    fPublicKey: TEccPublicKey;
  public
    /// initialize the verifier with a given ECC compressed public key
    constructor Create(const pub: TEccPublicKey); virtual;
    /// validate a signature against a hash using ECC
    function Verify(const hash: TEccHash; const sign: TEccSignature): boolean;
      virtual; abstract;
    /// the public key as specified to the class constructor
    property PublicKey: TEccPublicKey
      read fPublicKey;
  end;

  /// meta-clas of ECDSA signature verification class
  TEcc256r1VerifyClass = class of TEcc256r1VerifyAbstract;

  /// pascal verification of a ECDSA signature using ECC secp256r1 cryptography
  // - as implemented by ecdsa_verify_uncompressed_pas() in this unit
  TEcc256r1VerifyPas = class(TEcc256r1VerifyAbstract)
  protected
    fPub: TEccPublicKeyUncompressed;
  public
    /// initialize the verifier with a given ECC compressed public key
    constructor Create(const pub: TEccPublicKey); override;
    /// finalize this instance
    destructor Destroy; override;
    /// validate a signature against a hash using ECC
    function Verify(const hash: TEccHash; const sign: TEccSignature): boolean;
      override;
  end;


var
  /// fastest available class to be used to verify a ECDSA signature
  // - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
  // - direct low-level access to our pascal/asm version, or OpenSSL wrappers
  // - as used e.g. by TJwtEs256 high-level JWT processing class
  TEcc256r1Verify: TEcc256r1VerifyClass = TEcc256r1VerifyPas;


{ ***************** Middle-Level Certificate-based Public Key Cryptography }

type
  /// 128-bit (16 bytes) buffer used to identify a TEccCertificate
  // - could be generated by TAesPrng.Fill() method
  TEccCertificateID = type THash128;

  /// 128-bit (16 bytes) buffer  used to identify a TEccCertificate issuer
  // - could be generated by AsciiToBaudot(), with truncation to 16 bytes
  // (up to 25 Ascii-7 characters)
  TEccCertificateIssuer = type THash128;

  /// used to store a date in a TEccCertificate
  // - i.e. 16-bit number of days since 1 August 2016 - up to 2195
  // - use NowEccDate, EccDate(), EccToDateTime() or EccText() functions
  TEccDate = word;

  PEccCertificateID = ^TEccCertificateID;
  PEccCertificateIssuer = ^TEccCertificateIssuer;
  PEccDate = ^TEccDate;

  /// indicate the validity state of a ECDSA signature against a certificate
  // - as returned by low-level EccVerify() function, and
  // TEccSignatureCertified.Verify, TEccCertificateChain.IsValid or
  // TEccCertificateChain.IsSigned methods
  // - see also ECC_VALIDSIGN constant
  // - match TCertificateValidity enumerate in mormot.crypt.secure
  TEccValidity = (
    ecvUnknown,
    ecvValidSigned,
    ecvValidSelfSigned,
    ecvNotSupported,
    ecvBadParameter,
    ecvCorrupted,
    ecvInvalidDate,
    ecvUnknownAuthority,
    ecvDeprecatedAuthority,
    ecvInvalidSignature,
    ecvRevoked,
    ecvWrongUsage);

  /// the certification information of a TEccCertificate
  // - as stored in TEccCertificateContent.Head.Signed
  // - defined in a separate record, to be digitaly signed in the Signature field
  // - map TEccCertificate.Version 1 of the binary format
  // - "self-signed" certificates may be used as "root" certificates in the
  // TEccCertificateChain list
  TEccCertificateSigned = packed record
    /// when this certificate was generated
    IssueDate: TEccDate;
    /// certificate valid not before
    ValidityStart: TEccDate;
    /// certificate valid not after
    ValidityEnd: TEccDate;
    /// a 128-bit genuine identifier for this certificate
    // - is used later on to validate other certificates in chain
    Serial: TEccCertificateID;
    /// identify the certificate issuer
    // - is either geniune random bytes, or some Baudot-encoded text
    // - blank in V2, contains the (may be truncated) "subject" in V1 format
    Issuer: TEccCertificateIssuer;
    /// 128-bit genuine identifier of the authority certificate used for signing
    // - should be used to retrieve the associated PublicKey used to compute
    // the Signature field
    // - may equal Serial, if was self-signed
    AuthoritySerial: TEccCertificateID;
    /// identify the authoritify issuer used for signing
    // - is either geniune random bytes, or some Baudot-encoded text
    // - blank in V2, contains the (may be truncated) "subject" in V1 format
    AuthorityIssuer: TEccCertificateIssuer;
    /// the ECDSA secp256r1 public key of this certificate
    // - may be used later on for signing or key derivation
    PublicKey: TEccPublicKey;
  end;

  /// points to certification information of a TEccCertificate
  PEccCertificateSigned = ^TEccCertificateSigned;

  /// store the version 1 TEccCertificate binary buffer for ECC cryptography
  // - i.e. a certificate public key, with its ECDSA signature
  // - would be stored in 173 bytes
  TEccCertificateContentV1 = packed record
    /// the TEccCertificate format version
    // - 1 for mORMot 1 legacy layout, 2 for mORMot 2 with Usage + Issuer
    Version: word;
    /// the certification information, digitaly signed in the Signature field
    Signed: TEccCertificateSigned;
    /// SHA-256 + ECDSA secp256r1 signature of the Certificate record
    Signature: TEccSignature;
    /// FNV-1a checksum of all other fields
    // - we use fnv32 and not crc32c here to avoid colision with crc64c hashing
    // - avoiding to compute slow ECDSA verification in case of corruption,
    // due e.g. to unexpected transmission/bug/fuzzing/dosattack
    // - include V2 Info, as computed by TEccCertificateContent.ComputeCrc32
    CRC: cardinal;
  end;

  {$A-}

  /// up to 512 bytes of additional data for TEccCertificate binary version >= 2
  TEccCertificateContentV2 = record
    /// 16-bit storage for TCryptCertUsage
    Usage: word;
    /// 16-bit len of additional Data information
    DataLen: word;
    /// some additional data, e.g. the Subject, in up to 508 bytes
    // - such data will be stored with variable length
    Data: array[0..507] of byte;
  end;

  /// store a TEccCertificate binary buffer for ECC secp256r1 cryptography
  // - i.e. a certificate public key, with its ECDSA signature
  // - would be stored in 173 bytes (version 1) and 177+ bytes (version 2)
  {$ifdef USERECORDWITHMETHODS}
  TEccCertificateContent = record
  {$else}
  TEccCertificateContent = object
  {$endif USERECORDWITHMETHODS}
  public
    /// basic content - version 1 compatible
    Head: TEccCertificateContentV1;
    /// new version >= 2 with additional information (up to 512 bytes)
    Info: TEccCertificateContentV2;
    /// set Certificate usage, as 16-bit TCryptCertUsages value
    // - will also force the version to be 2 if maxversion allow it
    procedure SetUsage(usage: integer; maxversion: byte);
    /// get Certificate 16-bit TCryptCertUsage usage
    // - returns CU_ALL = all Usage for version 1
    function GetUsage: integer;
    /// set Certificate subject
    // - the input subject text could be CSV separated
    // - will first try to store it in the V1 Issuer field
    // - or switch to V2 and store after Baudot encoding into Info.Data - if
    // maxversion allow the upgrade
    procedure SetSubject(const sub: RawUtf8; maxversion: byte);
    /// get Certificate subject, after Baudot decoding from additional Info.Data
    function GetSubject: RawUtf8;
    /// fast check of the binary buffer storage of a certificate
    // - ensure CRC has the expected value, using FNV-1a checksum
    // - does not validate the certificate against the certificates chain, nor
    // perform any ECC signature: use TEccCertificateChain.IsValid instead
    function Check: boolean;
    /// fast check of the dates stored in a certificate binary buffer
    // - could be validated against EccCheck()
    // - you can specify your own UTC timestamp for expiration instead of NowUtc
    function CheckDate(nowdate: PEccDate = nil; TimeUtc: TDateTime = 0): boolean;
    /// fast check if the binary buffer storage of a certificate was self-signed
    // - a self-signed certificate has its AuthoritySerial field matching Seial
    function IsSelfSigned: boolean;
    /// compare all fields of both Certificates
    function FieldsEqual(const another: TEccCertificateContent): boolean;
    /// copy of the used bytes of TEccCertificateContent buffer
    procedure CopyTo(out dest: TEccCertificateContent);
    /// compute the FNV-32 digest of the whole content
    // - as stored in Head.CRC
    function ComputeCrc32: cardinal;
    /// compute the SHA-256 digest of the whole signed content
    procedure ComputeHash(out hash: TSha256Digest; const salt: RawByteString = '');
    /// serialize this certificate content as binary stream
    function SaveToStream(s: TStream): boolean;
    /// unserialize this certificate content from a binary stream
    function LoadFromStream(s: TStream; maxversion: byte): boolean;
  end;

  PEccCertificateContentV1 = ^TEccCertificateContentV1;
  PEccCertificateContentV2 = ^TEccCertificateContentV2;

  /// points to a TEccCertificate binary buffer for ECC secp256r1 cryptography
  PEccCertificateContent = ^TEccCertificateContent;

  /// store a TEccSignatureCertified binary buffer for ECDSA secp256r1 signature
  // - i.e. the digital signature of some content
  // - stored in 100 bytes, including full signature and authority information
  {$ifdef USERECORDWITHMETHODS}
  TEccSignatureCertifiedContent = record
  {$else}
  TEccSignatureCertifiedContent = object
  {$endif USERECORDWITHMETHODS}
  public
    /// the TEccSignatureCertificated format version
    Version: word;
    /// when this signature was generated
    Date: TEccDate;
    /// genuine identifier of the authority certificate used for signing
    // - should be used to retrieve the associated PublicKey used to compute
    // the Signature field
    AuthoritySerial: TEccCertificateID;
    /// identify the authoritify issuer used for signing
    // - is either geniune random bytes, or some Baudot-encoded text
    // - blank in V2, contains the (may be truncated) "subject" in V1 format
    AuthorityIssuer: TEccCertificateIssuer;
    /// SHA-256 + ECDSA secp256r1 digital signature of the content
    Signature: TEccSignature;
    /// fast check of the binary buffer storage of a signature
    // - just check that the date and authority are set
    function Check: boolean;
    /// convert a supplied Base64 text into a TEccSignatureCertifiedContent binary buffer
    function FromBase64(const base64: RawUtf8): boolean;
    /// convert a supplied TEccSignatureCertifiedContent binary buffer into proper text
    // - returns Base64 encoded text, or '' if the signature was filled with zeros
    function ToText: RawUtf8; overload;
    /// low-level verification of a TEccSignatureCertifiedContent binary buffer
    // - will verify all internal signature fields according to a supplied authority,
    // then will perform the ECDSA verification of the supplied 256-bit hash with
    // the authority public key
    // - optional authuncomp could be the uncompressed auth.Head.Signed.PublicKey
    function Verify(const hash: THash256; const auth: TEccCertificateContent;
      authuncomp: PEccPublicKeyUncompressed;
      TimeUtc: TDateTime = 0): TEccValidity; overload;
    /// low-level verification of a TEccSignatureCertifiedContent binary buffer
    // - will verify all internal signature fields according to a supplied authority
    // key, then perform the ECDSA verification of the supplied 256-bit hash with it
    // - returns ecvValidSigned on success, or an error value otherwise
    function Verify(const hash: THash256; const authkey: TEccPublicKey;
      valid: TEccValidity = ecvValidSigned;
      TimeUtc: TDateTime = 0): TEccValidity; overload;
  end;

  /// points to a TEccSignatureCertified buffer for ECDSA secp256r1 signature
  PEccSignatureCertifiedContent = ^TEccSignatureCertifiedContent;

  /// store a TEccCertificateChain Certificate Revocation List item
  // - would be stored as 24 bytes
  {$ifdef USERECORDWITHMETHODS}
  TEccCertificateRevocation = record
  {$else}
  TEccCertificateRevocation = object
  {$endif USERECORDWITHMETHODS}
  public
    /// contains the 65535 fixed number (ECC_REVOC_MAGIC)
    // - make a clear distinction with TEccCertificateContentV1.Version
    // - will be Base64-encoded as '/w...' so could be recognized from
    // a Base64-encoded TEccCertificate
    Magic: word;
    /// the Revocation format version
    // - currently equals 1
    Version: word;
    /// when this revocation becomes active
    Date: TEccDate;
    /// why this Certificate was revoked - usually TCryptCertRevocationReason
    Reason: word;
    /// the 128-bit (16 bytes) revocated Certificate Identifier
    Serial: TEccCertificateID;
    /// fast check of the binary buffer storage of a CRL item
    function Check: boolean;
    /// convert a supplied Base64 text into TEccCertificateRevocation binary buffer
    function FromBase64(const base64: RawUtf8): boolean;
    /// convert a TEccCertificateRevocation binary buffer into Base64 text
    function ToBase64: RawUtf8;
    /// setup a CRL item
    function From(const id: TEccCertificateID; dt: TDateTime; why: word): boolean;
    /// read TEccCertificateRevocation binary buffer from the given TStream
    function LoadFromStream(st: TStream): boolean;
    /// write the TEccCertificateRevocation binary buffer into the given TStream
    procedure SaveToStream(st: TStream);
  end;
  PEccCertificateRevocation = ^TEccCertificateRevocation;

  /// can store a whole Certificate Revocation List (CRL)
  TEccCertificateRevocationDynArray = array of TEccCertificateRevocation;

  {$A+}

  /// the error codes returned by TEccCertificateSecret.Decrypt()
  // - see also ECC_VALIDDECRYPT constant
  TEccDecrypt = (
    ecdDecrypted,
    ecdDecryptedWithSignature,
    ecdNoContent,
    ecdCorrupted,
    ecdInvalidSerial,
    ecdNoPrivateKey,
    ecdInvalidMAC,
    ecdDecryptError,
    ecdWriteFileError,
    ecdUnsupported);

const
  /// TEccValidity results indicating a valid digital signature
  ECC_VALIDSIGN =
    [ecvValidSigned, ecvValidSelfSigned];

  /// TEccDecrypt results indicating a valid decryption process
  ECC_VALIDDECRYPT =
    [ecdDecrypted, ecdDecryptedWithSignature];

  /// map all TCryptCertUsages flags for ECC Version 1 default value
  // - should match word(CU_ALL) from mormot.crypt.secure.pas
  ECCV1_USAGE_ALL = 65535;


function ToText(val: TEccValidity): PShortString; overload;
function ToText(res: TEccDecrypt): PShortString; overload;


/// fill all bytes of this ECC private key buffer with zero
// - may be used to cleanup stack-allocated content
// ! ... finally FillZero(PrivateKey); end;
procedure FillZero(out Priv: TEccPrivateKey); overload;

/// returns the current UTC date, as a TEccDate integer value
// - i.e. 16-bit number of days since 1 August 2016 - following UTC timing
function NowEccDate: TEccDate;
  {$ifdef HASINLINE}inline;{$endif}

/// convert a supplied TDateTime value into a TEccDate integer value
// - i.e. 16-bit number of days since 1 August 2016
// - returns 0 if the supplied value is invalid, i.e. out of range
function EccDate(const DateTime: TDateTime): TEccDate;

/// convert a supplied a TEccDate integer value into a TDateTime value
// - i.e. 16-bit number of days since 1 August 2016
function EccToDateTime(EccDate: TEccDate): TDateTime;
  {$ifdef HASINLINE}inline;{$endif}

/// convert a supplied a TEccDate integer value into a ISO-8601 text value
// - i.e. 16-bit number of days since 1 August 2016
function EccText(EccDate: TEccDate; Expanded: boolean = true): RawUtf8; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// compare two TEccCertificateIssuer binary buffer values
function IsEqual(const issuer1, issuer2: TEccCertificateIssuer): boolean; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// compare two TEccCertificateID binary buffer values
function IsEqual(const id1, id2: TEccCertificateID): boolean; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// ensure a TEccCertificateIssuer binary buffer is not void, i.e. filled with 0
function IsZero(const issuer: TEccCertificateIssuer): boolean; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// ensure a TEccCertificateID binary buffer is not void, i.e. filled with 0
function IsZero(const id: TEccCertificateID): boolean; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// ensure a TEccPublicKey binary buffer is not void, i.e. filled with 0
function IsZero(const k: TEccPublicKey): boolean; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// ensure a TEccPrivateKey binary buffer is not void, i.e. filled with 0
function IsZero(const pk: TEccPrivateKey): boolean; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// ensure a TEccSignature binary buffer is not void, i.e. filled with 0
function IsZero(const sig: TEccSignature): boolean; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// convert a supplied TEccCertificateIssuer binary buffer into proper text
// - returns Ascii-7 text if was stored using Baudot encoding
// - or returns hexadecimal values, if it was 16 bytes of random binary
function EccText(const Issuer: TEccCertificateIssuer): RawUtf8; overload;

/// convert some Ascii-7 text into a TEccCertificateIssuer binary buffer
// - using Emile Baudot encoding
// - returns TRUE on Text truncation to fit into the 16 bytes
function EccIssuer(const Text: RawUtf8; out Issuer: TEccCertificateIssuer;
  fullbaudot: PRawByteString = nil): boolean;

/// convert a supplied TEccCertificateID binary buffer into proper text
// - returns hexadecimal values, or '' if the ID is filled with zeros
function EccText(const ID: TEccCertificateID): RawUtf8; overload;

/// convert a supplied hexadecimal buffer into a TEccCertificateID binary buffer
// - returns TRUE if the supplied Text was a valid hexadecimal buffer
// - will also recognize GUID/UUID layout as {x-x-x-x} or x-x-x-x
function EccID(const Text: RawUtf8; out ID: TEccCertificateID): boolean;

/// convert a supplied TEccSignature binary buffer into proper text
// - returns Base64 encoded text, or '' if the signature was filled with zeros
function EccText(const sign: TEccSignature): RawUtf8; overload;


implementation


{ ***************** Low-Level ECC secp256r1 ECDSA and ECDH Functions }

{
  Optimized pascal adaptation of "simple and secure ECDH and ECDSA library"
   https://github.com/kmackay/micro-ecc
  Copyright (c) 2014, Kenneth MacKay - BSD 2-Clause "Simplified" License

 Notes:
 - our branchless pascal/asm version is faster than the original micro-ecc code
 - so we don't need nor publish .o / .obj static files any more
 - mormot.crypt.openssl.RegisterOpenSsl activates the much faster OpenSSL asm

 Some Numbers on Win32 Delphi:
 - mORMot  50 Ecc256r1MakeKey in 47.49ms i.e. 1K/s, aver. 949us
 - mORMot  50 Ecc256r1Sign in 48.38ms i.e. 1K/s, aver. 967us
 - mORMot  50 Ecc256r1Verify in 60.64ms i.e. 824/s, aver. 1.21ms
 - mORMot  98 Ecc256r1SharedSecret in 101.17ms i.e. 0.9K/s, aver. 1.03ms

 As historical reference, Win32 statically linked .obj with gcc -O2:
 - mORMot .obj  50 Ecc256r1MakeKey in 95.35ms i.e. 524/s, aver. 1.90ms
 - mORMot .obj  50 Ecc256r1Sign in 96.84ms i.e. 516/s, aver. 1.93ms
 - mORMot .obj  50 Ecc256r1Verify in 117.36ms i.e. 426/s, aver. 2.34ms
 - mORMot .obj  98 Ecc256r1SharedSecret in 201.53ms i.e. 486/s, aver. 2.05ms

 Some Numbers on Linux x86_64:
 - mORMot  300 Ecc256r1MakeKey in 76.59ms i.e. 3.8K/s, aver. 255us
 - mORMot  300 Ecc256r1Sign in 79.21ms i.e. 3.7K/s, aver. 264us
 - mORMot  300 Ecc256r1Verify in 95.70ms i.e. 3K/s, aver. 319us
 - mORMot  598 Ecc256r1SharedSecret in 158.93ms i.e. 3.6K/s, aver. 265us

 - OpenSSL 300 Ecc256r1MakeKey in   5.09ms i.e. 57.5K/s, aver. 16us
 - OpenSSL 300 Ecc256r1Sign in   7.97ms i.e. 36.7K/s, aver. 26us
 - OpenSSL 300 Ecc256r1Verify in  28.66ms i.e. 10.2K/s, aver. 95us
 - OpenSSL 598 Ecc256r1SharedSecret in 44.75ms i.e. 13K/s, aver. 74us

 Some Numbers on Linux AARCH64 (Oracle Cloud VM) :
 - mORMot  300 Ecc256r1MakeKey in 243.31ms i.e. 1.2K/s, aver. 811us
 - mORMot  300 Ecc256r1Sign in 250.06ms i.e. 1.1K/s, aver. 833us
 - mORMot  300 Ecc256r1Verify in 304.56ms i.e. 0.9K/s, aver. 1.01ms
 - mORMot  598 Ecc256r1SharedSecret in 523.25ms i.e. 1.1K/s, aver. 875us

 - OpenSSL 300 Ecc256r1MakeKey in 8.72ms i.e. 33.5K/s, aver. 29us
 - OpenSSL 300 Ecc256r1Sign in 13.92ms i.e. 21K/s, aver. 46us
 - OpenSSL 300 Ecc256r1Verify in 56.14ms i.e. 5.2K/s, aver. 187us
 - OpenSSL 598 Ecc256r1SharedSecret in 90.75ms i.e. 6.4K/s, aver. 151us
}

const
  ECC_QUAD = ECC_BYTES div 8; // = compute with 4 x 64-bit blocks

type
  TEccPoint = record
    x, y: THash256Rec;
  end;
  PEccPoint = ^TEccPoint;

const
  Curve_P_32: THash256Rec = (
   q: (QWord($FFFFFFFFFFFFFFFF),
       QWord($00000000FFFFFFFF),
       QWord($0000000000000000),
       QWord($FFFFFFFF00000001)));

  Curve_B_32: THash256Rec = (
    q: (QWord($3BCE3C3E27D2604B),
        QWord($651D06B0CC53B0F6),
        QWord($B3EBBD55769886BC),
        QWord($5AC635D8AA3A93E7)));

  Curve_G_32: TEccPoint = (
    x: (q:
        (QWord($F4A13945D898C296),
         QWord($77037D812DEB33A0),
         QWord($F8BCE6E563A440F2),
         QWord($6B17D1F2E12C4247)));
    y: (q:
        (QWord($CBB6406837BF51F5),
         QWord($2BCE33576B315ECE),
         QWord($8EE7EB4A7C0F9E16),
         QWord($4FE342E2FE1A7F9B))));

  Curve_N_32: THash256Rec = (
    q: (QWord($F3B9CAC2FC632551),
        QWord($BCE6FAADA7179E84),
        QWord($FFFFFFFFFFFFFFFF),
        QWord($FFFFFFFF00000000)));

  _1: THash256Rec = (q: (1, 0, 0, 0));

  _3: THash256Rec = (q: (3, 0, 0, 0));

  _11: THash256Rec = (q: (QWord($0101010101010101),
                          QWord($0101010101010101),
                          QWord($0101010101010101),
                          QWord($0101010101010101)));

procedure _set1(out V: THash256Rec);
  {$ifdef HASINLINE}inline;{$endif}
begin
  V.Q[0] := 1;
  V.Q[1] := 0;
  V.Q[2] := 0;
  V.Q[3] := 0;
end;

function _isZero(const V: THash256Rec): boolean;
  {$ifdef HASINLINE}inline;{$endif}
begin
  result := (V.Q[0] = 0) and
            (V.Q[1] = 0) and
            (V.Q[2] = 0) and
            (V.Q[3] = 0);
end;

function _equals(const Left, Right: THash256Rec): boolean;
  {$ifdef HASINLINE}inline;{$endif}
begin
  result := (Left.Q[0] = Right.Q[0]) and
            (Left.Q[1] = Right.Q[1]) and
            (Left.Q[2] = Right.Q[2]) and
            (Left.Q[3] = Right.Q[3]);
end;

procedure _mv(out Dest: THash256Rec; const Source: THash256Rec);
  {$ifdef HASINLINE}inline;{$endif}
begin
  {$ifdef CPU32}
  Dest := Source;
  {$else}
  Dest.Q[0] := Source.Q[0];
  Dest.Q[1] := Source.Q[1];
  Dest.Q[2] := Source.Q[2];
  Dest.Q[3] := Source.Q[3];
  {$endif CPU32}
end;


// computes result = (Left + Right) mod Modulo
// assumes that p_left < p_mod and p_right < p_mod, p_result != p_mod
procedure _modAddP(var Output: THash256Rec; const Left, Right: THash256Rec);
  {$ifdef HASINLINE}inline;{$endif}
begin
  if (_add256(Output, Left, Right) <> 0) or
     (_cmp256(Output, Curve_P_32) >= 0) then
    // result > Modulo (result = Modulo + Remainder),
    // so subtract Modulo to get remainder
    _dec256(Output, Curve_P_32);
end;

procedure _modAddN(var Output: THash256Rec; const Left, Right: THash256Rec);
  {$ifdef HASINLINE}inline;{$endif}
begin
  if (_add256(Output, Left, Right) <> 0) or
     (_cmp256(Output, Curve_N_32) >= 0) then
    // result > Modulo (result = Modulo + Remainder),
    // so subtract Modulo to get remainder
    _dec256(Output, Curve_N_32);
end;

// computes result = (Left - Right) mod Curve_P_32.
// assumes that Left < Curve_P_32 and Right < Curve_P_32 , result != Curve_P_32
procedure _modSubP(out Output: THash256Rec; const Left, Right: THash256Rec);
  {$ifdef HASINLINE}inline;{$endif}
begin
  if _sub256(Output, Left, Right) <> 0 then
    // In this case, Output == -diff == (max int) - diff.
    // Since -x mod d == d - x, we can get the correct result
    // from Output + Modulo (with overflow)
    _inc256(Output, Curve_P_32);
end;

// computes result = Product mod Curve_P_32
// from NIST https://tinyurl.com/3p5mt8kr
procedure _mmodP(out Output: THash256Rec; var Product: THash512Rec);
var
  carry: PtrInt;
  tmp: THash256Rec;
begin
  // t
  _mv(Output, Product.L);
  if _isZero(Product.H) and
     (_cmp256(Curve_P_32, Product.L) > 0) then
    exit; // no modulo to apply
  // s1
  tmp.Q[0] := 0;
  {$ifdef CPU32}
  tmp.C[2] := 0;
  tmp.C[3] := Product.C[11];
  {$else}
  tmp.Q[1] := Product.Q[5] and $FFFFFFFF00000000;
  {$endif CPU32}
  tmp.Q[2] := Product.Q[6];
  tmp.Q[3] := Product.Q[7];
  carry := _lshift1(tmp);
  inc(carry, _inc256(Output, tmp));
  // s2
  tmp.Q[1] := Product.Q[6] shl 32;
  tmp.Q[2] := (Product.Q[6] shr 32) or (Product.Q[7] shl 32);
  tmp.Q[3] := Product.Q[7] shr 32;
  inc(carry, _lshift1(tmp));
  inc(carry, _inc256(Output, tmp));
  // s3
  tmp.Q[0] := Product.Q[4];
  tmp.Q[1] := Product.Q[5] and $FFFFFFFF;
  tmp.Q[2] := 0;
  tmp.Q[3] := Product.Q[7];
  inc(carry, _inc256(Output, tmp));
  // s4
  tmp.Q[0] := (Product.Q[4] shr 32) or (Product.Q[5] shl 32);
  {$ifdef CPU32}
  tmp.C[2] := Product.C[11];
  tmp.C[3] := Product.C[13];
  {$else}
  tmp.Q[1] := (Product.Q[5] shr 32) or (Product.Q[6] and $FFFFFFFF00000000);
  {$endif CPU32}
  tmp.Q[2] := Product.Q[7];
  tmp.Q[3] := (Product.Q[6] shr 32) or (Product.Q[4] shl 32);
  inc(carry, _inc256(Output, tmp));
  // d1
  tmp.Q[0] := (Product.Q[5] shr 32) or (Product.Q[6] shl 32);
  tmp.Q[1] := (Product.Q[6] shr 32);
  tmp.Q[2] := 0;
  tmp.Q[3] := (Product.Q[4] and $FFFFFFFF) or (Product.Q[5] shl 32);
  dec(carry, _dec256(Output, tmp));
  // d2
  tmp.Q[0] := Product.Q[6];
  tmp.Q[1] := Product.Q[7];
  tmp.Q[2] := 0;
  {$ifdef CPU32}
  tmp.C[6] := Product.C[9];
  tmp.C[7] := Product.C[11];
  {$else}
  tmp.Q[3] := (Product.Q[4] shr 32) or (Product.Q[5] and $FFFFFFFF00000000);
  {$endif CPU32}
  dec(carry, _dec256(Output, tmp));
  // d3
  tmp.Q[0] := (Product.Q[6] shr 32) or (Product.Q[7] shl 32);
  tmp.Q[1] := (Product.Q[7] shr 32) or (Product.Q[4] shl 32);
  tmp.Q[2] := (Product.Q[4] shr 32) or (Product.Q[5] shl 32);
  tmp.Q[3] := (Product.Q[6] shl 32);
  dec(carry, _dec256(Output, tmp));
  // d4
  tmp.Q[0] := Product.Q[7];
  tmp.Q[2] := Product.Q[5];
  {$ifdef CPU32}
  tmp.C[2] := 0;
  tmp.C[3] := Product.C[9];
  tmp.C[6] := 0;
  tmp.C[7] := Product.C[13];
  {$else}
  tmp.Q[1] := Product.Q[4] and $FFFFFFFF00000000;
  tmp.Q[3] := Product.Q[6] and $FFFFFFFF00000000;
  {$endif CPU32}
  dec(carry, _dec256(Output, tmp));
  if carry < 0 then
    repeat
      inc(carry, _inc256(Output, Curve_P_32));
    until carry >= 0
  else
    while (carry <> 0) or
          (_cmp256(Curve_P_32, Output) <= 0) do
      dec(carry, _dec256(Output, Curve_P_32));
end;

// computes result = (Left * Right) mod Curve
procedure _modMultP(out Output: THash256Rec; const Left, Right: THash256Rec);
  {$ifdef HASINLINE}inline;{$endif}
var
  product: THash512Rec;
begin
  _mult256(product, Left, Right);
  _mmodP(Output, product);
end;

// computes result = Left^2 mod Curve
procedure _modSquareP(out Output: THash256Rec; const Left: THash256Rec);
  {$ifdef HASINLINE}inline;{$endif}
var
  product: THash512Rec;
begin
  _square256(product, Left);
  _mmodP(Output, product);
end;

// computes result = (1 / p_input) mod Modulo. All VLIs are the same size
// See "From Euclid's GCD to Montgomery Multiplication to the Great Divide"
// https://labs.oracle.com/techrep/2001/smli_tr-2001-95.pdf
procedure _modInv(out Output: THash256Rec; const Input, Modulo: THash256Rec);
var
  a, b, v: THash256Rec;
  carry: PtrUInt;
  cmp: integer;
begin
  if _isZero(Input) then
  begin
    FillZero(Output.b);
    exit;
  end;
  _mv(a, Input);
  _mv(b, Modulo);
  _set1(Output);
  FillZero(v.b);
  repeat
    cmp := _cmp256(a, b);
    if cmp = 0 then
      break;
    carry := 0;
    if (a.C[0] and 1) = 0 then
    begin
      _rshift1(a);
      if (Output.C[0] and 1) = 1 then
        carry := _inc256(Output, Modulo);
      _rshift1(Output);
      if carry <> 0 then
        Output.B[ECC_BYTES - 1] := Output.B[ECC_BYTES - 1] or $80;
    end
    else if (b.C[0] and 1) = 0 then
    begin
      _rshift1(b);
      if (v.C[0] and 1) = 1 then
        carry := _add256(v, v, Modulo);
      _rshift1(v);
      if carry <> 0 then
        v.B[ECC_BYTES - 1] := v.B[ECC_BYTES - 1] or $80;
    end
    else if cmp > 0 then
    begin
      _dec256(a, b);
      _rshift1(a);
      if _cmp256(Output, v) < 0 then
        _inc256(Output, Modulo);
      _dec256(Output, v);
      if (Output.C[0] and 1) = 1 then
        carry := _inc256(Output, Modulo);
      _rshift1(Output);
      if carry <> 0 then
        Output.B[ECC_BYTES - 1] := Output.B[ECC_BYTES - 1] or $80;
    end
    else
    begin
      _dec256(b, a);
      _rshift1(b);
      if _cmp256(v, Output) < 0 then
        _inc256(v, Modulo);
      _dec256(v, Output);
      if (v.C[0] and 1) = 1 then
        carry := _inc256(v, Modulo);
      _rshift1(v);
      if carry > 0 then
        v.B[ECC_BYTES - 1] := v.B[ECC_BYTES - 1] or $80;
    end;
  until false;
end;

// Point multiplication algorithm using Montgomery's ladder with co-Z coordinates.
// From http://eprint.iacr.org/2011/338.pdf

// Double in place
procedure EccPointDoubleJacobian(var X1, Y1, Z1: THash256Rec);
var
  carry: QWord;
  t4, t5: THash256Rec;
begin
  // t1 = X, t2 = Y, t3 = Z
  if _isZero(Z1) then
    exit;
  _modSquareP(t4, Y1);         // t4 = y1^2
  _modMultP(t5, X1, t4);       // t5 = x1*y1^2 = A
  _modSquareP(t4, t4);         // t4 = y1^4
  _modMultP(Y1, Y1, Z1);       // t2 = y1*z1 = z3
  _modSquareP(Z1, Z1);         // t3 = z1^2
  _modAddP(X1, X1, Z1);        // t1 = x1 + z1^2
  _modAddP(Z1, Z1, Z1);        // t3 = 2*z1^2
  _modSubP(Z1, X1, Z1);        // t3 = x1 - z1^2
  _modMultP(X1, X1, Z1);       // t1 = x1^2 - z1^4
  _modAddP(Z1, X1, X1);        // t3 = 2*(x1^2 - z1^4)
  _modAddP(X1, X1, Z1);        // t1 = 3*(x1^2 - z1^4)
  if GetBitPtr(@X1, 0) then
  begin
    carry := _inc256(X1, Curve_P_32);
    _rshift1(X1);
    X1.Q[ECC_QUAD - 1] := X1.Q[ECC_QUAD - 1] or (carry shl 63);
  end
  else
    _rshift1(X1);
  // t1 = 3/2*(x1^2 - z1^4) = B
  _modSquareP(Z1, X1);         // t3 = B^2
  _modSubP(Z1, Z1, t5);        // t3 = B^2 - A
  _modSubP(Z1, Z1, t5);        // t3 = B^2 - 2A = x3
  _modSubP(t5, t5, Z1);        // t5 = A - x3
  _modMultP(X1, X1, t5);       // t1 = B * (A - x3)
  _modSubP(t4, X1, t4);        // t4 = B * (A - x3) - y1^4 = y3
  _mv(X1, Z1);
  _mv(Z1, Y1);
  _mv(Y1, t4);
end;

// Modify (x1, y1) => (x1 * z^2, y1 * z^3)
procedure _apply_z(var X1, Y1, Z: THash256Rec);
var
  t1: THash256Rec;
begin
  _modSquareP(t1, Z);        // z^2
  _modMultP(X1, X1, t1);     // x1 * z^2
  _modMultP(t1, t1, Z);      // z^3
  _modMultP(Y1, Y1, t1);     // y1 * z^3
end;

// P = (x1, y1) => 2P, (x2, y2) => P'
procedure _XYcZ_initial_double(var X1, Y1, X2, Y2: THash256Rec;
  InitialZ: PHash256Rec);
var
  z: THash256Rec;
begin
  _mv(X2, X1);
  _mv(Y2, Y1);
  if InitialZ = nil then
    InitialZ := @_1;
  _mv(z, InitialZ^);
  _apply_z(X1, Y1, z);
  EccPointDoubleJacobian(X1, Y1, z);
  _apply_z(X2, Y2, z);
end;

// Input P = (x1, y1, Z), Q = (x2, y2, Z)
// Output P' = (x1', y1', Z3), P + Q = (x3, y3, Z3)
//     or P => P', Q => P + Q
procedure _XYcZ_add(var X1, Y1, X2, Y2: THash256Rec);
var
  t5: THash256Rec;
begin
  // t1 = X1, t2 = Y1, t3 = X2, t4 = Y2
  _modSubP(t5, X2, X1);        // t5 = x2 - x1
  _modSquareP(t5, t5);         // t5 = (x2 - x1)^2 = A
  _modMultP(X1, X1, t5);       // t1 = x1*A = B
  _modMultP(X2, X2, t5);       // t3 = x2*A = C
  _modSubP(Y2, Y2, Y1);        // t4 = y2 - y1
  _modSquareP(t5, Y2);         // t5 = (y2 - y1)^2 = D
  _modSubP(t5, t5, X1);        // t5 = D - B
  _modSubP(t5, t5, X2);        // t5 = D - B - C = x3
  _modSubP(X2, X2, X1);        // t3 = C - B
  _modMultP(Y1, Y1, X2);       // t2 = y1*(C - B)
  _modSubP(X2, X1, t5);        // t3 = B - x3
  _modMultP(Y2, Y2, X2);       // t4 = (y2 - y1)*(B - x3)
  _modSubP(Y2, Y2, Y1);        // t4 = y3
  _mv(X2, t5);
end;

// Input P = (x1, y1, Z), Q = (x2, y2, Z)
//  Output P + Q = (x3, y3, Z3), P - Q = (x3', y3', Z3)
//      or P => P - Q, Q => P + Q
procedure _XYcZ_addC(var X1, Y1, X2, Y2: THash256Rec);
var
  t5, t6, t7: THash256Rec;
begin
  // t1 = X1, t2 = Y1, t3 = X2, t4 = Y2
  _modSubP(t5, X2, X1);        // t5 = x2 - x1
  _modSquareP(t5, t5);         // t5 = (x2 - x1)^2 = A
  _modMultP(X1, X1, t5);       // t1 = x1*A = B
  _modMultP(X2, X2, t5);       // t3 = x2*A = C
  _modAddP(t5, Y2, Y1);        // t4 = y2 + y1
  _modSubP(Y2, Y2, Y1);        // t4 = y2 - y1
  _modSubP(t6, X2, X1);        // t6 = C - B
  _modMultP(Y1, Y1, t6);       // t2 = y1 * (C - B)
  _modAddP(t6, X1, X2);        // t6 = B + C
  _modSquareP(X2, Y2);         // t3 = (y2 - y1)^2
  _modSubP(X2, X2, t6);        // t3 = x3
  _modSubP(t7, X1, X2);        // t7 = B - x3
  _modMultP(Y2, Y2, t7);       // t4 = (y2 - y1)*(B - x3)
  _modSubP(Y2, Y2, Y1);        // t4 = y3
  _modSquareP(t7, t5);         // t7 = (y2 + y1)^2 = F
  _modSubP(t7, t7, t6);        // t7 = x3'
  _modSubP(t6, t7, X1);        // t6 = x3' - B
  _modMultP(t6, t6, t5);       // t6 = (y2 + y1)*(x3' - B)
  _modSubP(Y1, t6, Y1);        // t2 = y3'
  _mv(X1, t7);
end;

procedure EccPointMult(out Output: TEccPoint; const Point: TEccPoint;
  const Scalar: THash256Rec; InitialZ: PHash256Rec);
var
  Rx, Ry: array[0..1] of THash256Rec;
  z: THash256Rec;
  i, nb: PtrInt;
begin
  // R0 and R1
  _mv(Rx[1], Point.x);
  _mv(Ry[1], Point.y);
  _XYcZ_initial_double(Rx[1], Ry[1], Rx[0], Ry[0], InitialZ);
  for i := _numbits256(Scalar) - 2 downto 1 do
  begin
    nb := ord(not GetBitPtr(@Scalar, i));
    _XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb]);
    _XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb]);
  end;
  nb := ord(not GetBitPtr(@Scalar, 0));
  _XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb]);
  // Find final 1/Z value
  _modSubP(z, Rx[1], Rx[0]);         // X1 - X0
  _modMultP(z, z, Ry[1 - nb]);       // Yb * (X1 - X0)
  _modMultP(z, z, Point.x);          // xP * Yb * (X1 - X0)
  _modInv(z, z, Curve_P_32);         // 1 / (xP * Yb * (X1 - X0))
  _modMultP(z, z, Point.y);          // yP / (xP * Yb * (X1 - X0))
  _modMultP(z, z, Rx[1 - nb]);       // Xb * yP / (xP * Yb * (X1 - X0))
  // End 1/Z calculation
  _XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb]);
  _apply_z(Rx[0], Ry[0], z);
  _mv(Output.x, Rx[0]);
  _mv(Output.y, Ry[0]);
end;

// Compute a = sqrt(a) (mod curve_p)
procedure ModSqrt(var a: THash256Rec);
var
  i: integer;
  p1, result: THash256Rec;
begin
  _set1(result);
  // Since curve_p == 3 (mod 4) for all supported curves, we can compute
  // sqrt(a) = a^((curve_p + 1) / 4) (mod curve_p)
  _add256(p1, Curve_P_32, _1); // p1 = curve_p + 1
  for i := _numbits256(p1) - 1 downto 2 do
  begin
    _modSquareP(result, result);
    if GetBitPtr(@p1, i) then
      _modMultP(result, result, a);
  end;
  _mv(a, result);
end;

procedure EccPointDecompress(out Point: TEccPoint; const Compressed: TEccPublicKey);
begin
  _bswap256(@Point.x, @Compressed[1]);
  _modSquareP(Point.y, Point.x);           // y = x^2
  _modSubP(Point.y, Point.y, _3);          // y = x^2 - 3
  _modMultP(Point.y, Point.y, Point.x);    // y = x^3 - 3x
  _modAddP(Point.y, Point.y, Curve_B_32);  // y = x^3 - 3x + b
  ModSqrt(Point.y);
  if (Point.y.C[0] and 1) <> (cardinal(Compressed[0]) and 1) then
    _sub256(Point.y, Curve_P_32, Point.y);
end;

const
  MAX_TRIES = 16; // work almost always on the first trial with TAesPrng

function ecc_make_key_pas(out PublicKey: TEccPublicKey;
  out PrivateKey: TEccPrivateKey): boolean;
var
  priv: THash256Rec;
  pub: TEccPoint;
  tries: integer;
begin
  result := false;
  tries := MAX_TRIES;
  repeat
    dec(tries);
    if tries = 0 then
      exit;
    TAesPrng.Fill(THash256(priv));
    if _isZero(priv) or
       _equals(priv, _1) or
       _equals(priv, _11) then
      continue;
    // Make sure the private key is in the range [1, n - 1]
    // For the supported curves, n is always large enough that we only need
    // to subtract once at most
    if _cmp256(Curve_N_32, priv) <= 0 then
      _dec256(priv, Curve_N_32);
    EccPointMult(pub, Curve_G_32, priv, nil);
  until not (_isZero({%H-}pub.x) and _isZero(pub.y));
  _bswap256(@PrivateKey, @priv);
  Ecc256r1Compress(TEccPublicKeyUncompressed(pub), PublicKey);
  result := true;
  FillZero(priv.b); // erase sensitive information from stack
  FillZero(THash512(pub));
end;

procedure Ecc256r1Compress(const Uncompressed: TEccPublicKeyUncompressed;
  out Compressed: TEccPublicKey);
begin
  // use standard compressed form header and byte order
  Compressed[0] := 2 + (TEccPoint(Uncompressed).y.B[0] and 1);
  _bswap256(@Compressed[1], @TEccPoint(Uncompressed).x);
end;

procedure Ecc256r1PublicFromPrivate(const PrivateKey: TEccPrivateKey;
  out PublicKey: TEccPublicKey);
var
  priv: THash256Rec;
  pub: TEccPoint;
begin
  _bswap256(@priv, @PrivateKey);
  EccPointMult(pub, Curve_G_32, priv, nil); // see ecc_make_key_pas() above
  Ecc256r1Compress(TEccPublicKeyUncompressed(pub), PublicKey);
  FillZero(priv.b); // erase sensitive information from stack
  FillZero(THash512(pub));
end;

function Ecc256r1MatchKeys(const PrivateKey: TEccPrivateKey;
  const PublicKey: TEccPublicKey): boolean;
var
  pub: TEccPublicKey;
begin
  Ecc256r1PublicFromPrivate(PrivateKey, pub);
  result := CompareMem(@pub, @PublicKey, SizeOf(pub));
end;

function Ecc256r1ExtractAsn1(const Asn1: RawByteString;
  out Uncompressed: TEccPublicKeyUncompressed): boolean;
var
  len: integer;
  p: PHash512Rec;
  u: THash512Rec absolute Uncompressed;
begin
  result := false;
  FillCharFast(Uncompressed, SizeOf(Uncompressed), 0);
  len := length(Asn1);
  p := pointer(Asn1);
  if (len = SizeOf(TEccPublicKeyUncompressed) + 1) and
     (p.b[0] = 4) then
  begin
    inc(PByte(p)); // ignore $04 ASN-1 uncompressed public key marker
    dec(len);
  end;
  if len <> SizeOf(TEccPublicKeyUncompressed) then
    exit;
  _bswap256(@u.l, @p.l); // change endianness
  _bswap256(@u.h, @p.h);
  result := true;
end;

function Ecc256r1CompressAsn1(const Uncompressed: RawByteString;
  out Compressed: TEccPublicKey): boolean;
var
  u: TEccPublicKeyUncompressed;
begin
  result := Ecc256r1ExtractAsn1(Uncompressed, u);
  if result then
    Ecc256r1Compress(u, Compressed);
end;

function Ecc256r1UncompressAsn1(const Compressed: TEccPublicKey): RawByteString;
var
  p: PHash512Rec;
  u: THash512Rec;
begin
  result := '';
  if IsZero(Compressed) then
    exit;
  SetLength(result, SizeOf({%H-}p^) + 1);
  p := pointer(result);
  p.b[0] := $04; // ASN-1 uncompressed public key marker
  inc(PByte(p));
  Ecc256r1Uncompress(Compressed, PEccPublicKeyUncompressed(@u)^);
  _bswap256(@p.l, @u.l);
  _bswap256(@p.h, @u.h);
end;

function ecdh_shared_secret_uncompressed_pas(
  const PublicPoint: TEccPublicKeyUncompressed;
  const PrivateKey: TEccPrivateKey; out Secret: TEccSecretKey): boolean;
var
  priv: THash256Rec;
  product: TEccPoint;
  rnd: THash256Rec;
begin
  TAesPrng.Fill(THash256(rnd));
  _bswap256(@priv, @PrivateKey);
  EccPointMult(product, TEccPoint(PublicPoint), priv, @rnd);
  _bswap256(@Secret, @product.x);
  result := not (_isZero(product.x) and _isZero(product.y));
  FillZero(priv.b); // erase sensitive information from stack
  FillZero(rnd.b);
  FillZero(THash512(product));
end;

procedure ecc_uncompress_key_pas(const Compressed: TEccPublicKey;
  out Uncompressed: TEccPublicKeyUncompressed);
begin
  EccPointDecompress(TEccPoint(Uncompressed), Compressed);
end;

function ecdh_shared_secret_pas(const PublicKey: TEccPublicKey;
  const PrivateKey: TEccPrivateKey; out Secret: TEccSecretKey): boolean;
var
  pub: TEccPublicKeyUncompressed;
begin
  EccPointDecompress(TEccPoint(pub), PublicKey);
  result := ecdh_shared_secret_uncompressed_pas(pub, PrivateKey, Secret);
end;

// computes result = (Left * Right) mod Curve_N_32
procedure _modMultN(out Output: THash256Rec; const Left, Right: THash256Rec);
var
  carry: QWord;
  cmp: integer;
  modbig, product: THash512Rec;
  digits, bits, prodbits: integer;
  v: PHash256Rec;
const
  modbits = 256; // _numBits(Curve_N_32);
begin
  _mult256(product, Left, Right);
  prodbits := _numbits256(product.H);
  if prodbits <> 0 then
    inc(prodbits, ECC_QUAD * 64)
  else
    prodbits := _numbits256(product.L);
  if prodbits < modbits then
  begin
    // l_product < p_mod
    _mv(Output, product.L);
    exit;
  end;
  // Shift p_mod by (LeftBits - modbits). This multiplies p_mod by the largest
  // power of two possible while still resulting in a number less than p_left
  FillZero(modbig.b);
  digits := (prodbits - modbits) shr 6;
  bits   := (prodbits - modbits) and 63;
  v := @modbig.Q[digits];
  if bits > 0 then
    modbig.Q[digits + ECC_QUAD] := _lshift(v^, Curve_N_32, bits)
  else
    _mv(v^, Curve_N_32);
  // Subtract all multiples of Modulo to get the remainder
  while (prodbits > ECC_QUAD * 64) or
        (_cmp256(modbig.L, Curve_N_32) >= 0) do
  begin
    cmp := _cmp256(modbig.H, product.H);
    if (cmp < 0) or
       ((cmp = 0) and
        (_cmp256(modbig.L, product.L) <= 0)) then
    begin
      if _dec256(product.L, modbig.L) <> 0 then
        _dec256(product.H, _1); // borrow
      _dec256(product.H, modbig.H);
    end;
    carry := (modbig.Q[ECC_QUAD] and 1) shl 63;
    _rshift1(modbig.H);
    _rshift1(modbig.L);
    if carry <> 0 then
      modbig.Q[ECC_QUAD - 1] := modbig.Q[ECC_QUAD - 1] or carry;
    dec(prodbits);
  end;
  _mv(Output, product.L);
end;

function ecdsa_sign_pas(const PrivateKey: TEccPrivateKey; const Hash: TEccHash;
  out Signature: TEccSignature): boolean;
var
  k, temp, S: THash256Rec;
  P: TEccPoint;
  tries: integer;
begin
  result := false;
  tries := 0;
  repeat
    inc(tries);
    TAesPrng.Fill(THash256(k));
    if tries >= MAX_TRIES then
      exit; // the random generator seems broken
    if _isZero(k) or
       _equals(k, _1) or
       _equals(k, _11) then
      continue;
    if _cmp256(Curve_N_32, k) <= 0 then
      _dec256(k, Curve_N_32);
    // temp = k * G
    EccPointMult(P, Curve_G_32, k, nil);
    // r = x1 (mod n)
    if _cmp256(Curve_N_32, P.x) <= 0 then
      _dec256(P.x, Curve_N_32);
  until not _isZero({%H-}P.x);
  _bswap256(@Signature, @P.x);
  _bswap256(@temp, @PrivateKey);
  _modMultN(S, P.x, temp);     // s = r*d
  _bswap256(@temp, @Hash);
  _modAddN(S, temp, S);        // s = e + r*d
  _modInv(k, k, Curve_N_32);   // k = 1 / k
  _modMultN(S, S, k);          // s = (e + r*d) / k
  _bswap256(@Signature[ECC_BYTES], @S);
  result := true;
end;

function ecdsa_verify_uncompressed_pas(const PublicKey: TEccPublicKeyUncompressed;
  const Hash: TEccHash; const Signature: TEccSignature): boolean;
var
  i, index, numbits: integer;
  pub: TEccPoint absolute PublicKey;
  sumpt: TEccPoint;
  pt: PEccPoint;
  pts: array[0..3] of PEccPoint;
  rx, ry, tx, ty, tz, l_r, l_s, u1, u2, z: THash256Rec;
begin
  result := false;
  _bswap256(@l_r, @Signature);
  _bswap256(@l_s, @Signature[ECC_BYTES]);
  if _isZero(l_r) or
     _isZero(l_s) or
     (_cmp256(Curve_N_32, l_r) <= 0) or
     (_cmp256(Curve_N_32, l_s) <= 0) then
    exit; // r, s must be <> 0 and < n
  // calculate u1 and u2
  _modInv(z, l_s, Curve_N_32);      // Z = s^-1
  _bswap256(@u1, @Hash);
  _modMultN(u1, u1, z);             // u1 = e/s
  _modMultN(u2, l_r, z);            // u2 = r/s
  // calculate l_sum = G + Q
  _mv(sumpt.x, pub.x);
  _mv(sumpt.y, pub.y);
  _mv(tx, Curve_G_32.x);
  _mv(ty, Curve_G_32.y);
  _modSubP(z, sumpt.x, tx);               // Z = x2 - x1
  _XYcZ_add(tx, ty, sumpt.x, sumpt.y);
  _modInv(z, z, Curve_P_32);              // Z = 1/Z
  _apply_z(sumpt.x, sumpt.y, z);
  // use Shamir's trick to calculate u1*G + u2*Q
  pts[0] := nil;
  pts[1] := @Curve_G_32;
  pts[2] := @pub;
  pts[3] := @sumpt;
  numbits := _numbits256(u1);
  index := _numbits256(u2);
  if index > numbits then
    numbits := index;
  index := ord(GetBitPtr(@u1, numbits - 1)) +
           ord(GetBitPtr(@u2, numbits - 1)) * 2;
  pt := pts[index];
  _mv(rx, pt^.x);
  _mv(ry, pt^.y);
  _set1(z);
  for i := numbits - 2 downto 0 do
  begin
    EccPointDoubleJacobian(rx, ry, z);
    index := ord(GetBitPtr(@u1, i)) +
             ord(GetBitPtr(@u2, i)) * 2;
    pt := pts[index];
    if pt <> nil then
    begin
      _mv(tx, pt^.x);
      _mv(ty, pt^.y);
      _apply_z(tx, ty, z);
      _modSubP(tz, rx, tx); // Z = x2 - x1
      _XYcZ_add(tx, ty, rx, ry);
      _modMultP(z, z, tz);
    end;
  end;
  _modInv(z, z, Curve_P_32); // Z = 1/Z
  _apply_z(rx, ry, z);
  // v = x1 (mod n)
  if _cmp256(Curve_N_32, rx) <= 0 then
    _dec256(rx, Curve_N_32);
  result := IsEqual(rx.b, l_r.b); // Accept only if v == r
end;

function ecdsa_verify_pas(const PublicKey: TEccPublicKey; const Hash: TEccHash;
  const Signature: TEccSignature): boolean;
var
  pub: TEccPublicKeyUncompressed;
begin
  EccPointDecompress(TEccPoint(pub), PublicKey);
  result := ecdsa_verify_uncompressed_pas(pub, Hash, Signature);
end;


{ TEcc256r1VerifyAbstract }

constructor TEcc256r1VerifyAbstract.Create(const pub: TEccPublicKey);
begin
  fPublicKey := pub;
end;


{ TEcc256r1VerifyPas }

constructor TEcc256r1VerifyPas.Create(const pub: TEccPublicKey);
begin
  inherited Create(pub);
  EccPointDecompress(TEccPoint(fPub), pub);
end;

destructor TEcc256r1VerifyPas.Destroy;
begin
  inherited Destroy;
  FillZero(THash512(fPub));
end;

function TEcc256r1VerifyPas.Verify(const hash: TEccHash;
  const sign: TEccSignature): boolean;
begin
  result := ecdsa_verify_uncompressed_pas(fPub, Hash, sign);
end;



{ ***************** Middle-Level Certificate-based Public Key Cryptography }

function ToText(val: TEccValidity): PShortString;
begin
  result := GetEnumName(TypeInfo(TEccValidity), ord(val));
end;

function ToText(res: TEccDecrypt): PShortString;
begin
  result := GetEnumName(TypeInfo(TEccDecrypt), ord(res));
end;

procedure FillZero(out Priv: TEccPrivateKey);
begin
  PInt64Array(@Priv)^[0] := 0;
  PInt64Array(@Priv)^[1] := 0;
  PInt64Array(@Priv)^[2] := 0;
  PInt64Array(@Priv)^[3] := 0;
end;

function IsEqual(const issuer1, issuer2: TEccCertificateIssuer): boolean;
var
  a: TPtrIntArray absolute issuer1;
  b: TPtrIntArray absolute issuer2;
begin
  result := (a[0] = b[0]) and
            (a[1] = b[1])
            {$ifdef CPU32} and
            (a[2] = b[2]) and
            (a[3] = b[3])
            {$endif CPU32};
end;

function IsEqual(const id1, id2: TEccCertificateID): boolean;
var
  a: TPtrIntArray absolute id1;
  b: TPtrIntArray absolute id2;
begin
  result := (a[0] = b[0]) and
            (a[1] = b[1])
            {$ifdef CPU32} and
            (a[2] = b[2]) and
            (a[3] = b[3])
            {$endif CPU32};
end;

function IsZero(const issuer: TEccCertificateIssuer): boolean;
var
  a: TPtrIntArray absolute issuer;
begin
  result := (a[0] = 0) and
            (a[1] = 0)
            {$ifdef CPU32} and
            (a[2] = 0) and
            (a [3] = 0)
            {$endif CPU32};
end;

function IsZero(const id: TEccCertificateID): boolean;
var
  a: TPtrIntArray absolute id;
begin
  result := (a[0] = 0) and
            (a[1] = 0)
            {$ifdef CPU32} and
            (a[2] = 0) and
            (a [3] = 0)
            {$endif CPU32};
end;

function IsZero(const k: TEccPublicKey): boolean;
begin
  result := IsZero(PHash256(@k[1])^); // checking main 256-bit is enough
end;

function IsZero(const pk: TEccPrivateKey): boolean;
begin
  result := IsZero(THash256(pk));
end;

function IsZero(const sig: TEccSignature): boolean;
begin
  result := IsZero(THash512Rec(sig).Lo) or // any 0 coordinate is invalid
            IsZero(THash512Rec(sig).Hi);
end;

function NowEccDate: TEccDate;
begin
  result := Trunc(NowUtc) - ECC_DELTA;
end;

function EccDate(const DateTime: TDateTime): TEccDate;
var
  now: integer;
begin
  if DateTime = 0 then
    result := 0
  else
  begin
    now := Trunc(DateTime) - ECC_DELTA;
    if cardinal(now) > high(TEccDate) then
      result := 0
    else
      result := now;
  end;
end;

function EccToDateTime(EccDate: TEccDate): TDateTime;
begin
  if EccDate = 0 then
    result := 0
  else
    result := EccDate + ECC_DELTA;
end;

function EccText(EccDate: TEccDate; Expanded: boolean): RawUtf8;
begin
  if EccDate = 0 then
    result := ''
  else
    result := DateToIso8601(EccDate + ECC_DELTA, Expanded);
end;

function EccText(const Issuer: TEccCertificateIssuer): RawUtf8;
var
  tmp: array[0..1] of TEccCertificateIssuer;
begin
  if IsZero(Issuer) then
    result := ''
  else
  begin
    tmp[0] := Issuer;
    tmp[1][0] := 0; // add a trailing #0 as expected for trailing bits
    result := BaudotToAscii(@tmp, SizeOf(Issuer));
    if result = '' then
      result := mormot.core.text.BinToHex(@Issuer, SizeOf(Issuer));
  end;
end;

function EccIssuer(const Text: RawUtf8; out Issuer: TEccCertificateIssuer;
  fullbaudot: PRawByteString): boolean;
var
  baudot: RawByteString;
  len: integer;
begin
  FillZero(THash128(Issuer));
  baudot := AsciiToBaudot(Text);
  if fullbaudot <> nil then
    fullbaudot^ := baudot;
  len := length(baudot);
  result := len > SizeOf(Issuer);
  if result then // truncated
    len := SizeOf(Issuer);
  MoveFast(pointer(baudot)^, Issuer, len);
end;

function EccText(const ID: TEccCertificateID): RawUtf8;
begin
  if IsZero(ID) then
    result := ''
  else
    result := AesBlockToString(TAesBlock(ID));
end;

function EccID(const Text: RawUtf8; out ID: TEccCertificateID): boolean;
var
  tmp: RawUtf8;
begin
  tmp := Text;
  result := ((length(tmp) = 32) or TrimGuid(tmp)) and
            mormot.core.text.HexToBin(pointer(tmp), @ID, SizeOf(ID))
end;

function EccText(const sign: TEccSignature): RawUtf8;
begin
  if IsZero(sign) then
    result := ''
  else
    result := BinToBase64(@sign, SizeOf(sign));
end;


{ TEccCertificateContent }

procedure TEccCertificateContent.SetUsage(usage: integer; maxversion: byte);
begin
  if Head.Version = 1 then
    if (usage = ECCV1_USAGE_ALL) or
       (maxversion < 2) then
      exit // V1 will assume all usages
    else
      Head.Version := 2; // we need the new format
  Info.Usage := usage;
end;

function TEccCertificateContent.GetUsage: integer;
begin
  if Head.Version = 1 then
    result := ECCV1_USAGE_ALL // all usages
  else
    result := Info.Usage;
end;

type
  TInfoV2 = record // structure to encode/decode TEccCertificateContentV2
    Subject: RawUtf8;
  end;

procedure Decode(const info: TEccCertificateContentV2; out v2: TInfoV2);
var
  s, smax: PByte;
begin
  s := @info.Data;
  smax := @PByteArray(s)[info.DataLen];
  v2.Subject := FromVarString(s, smax);
end;

procedure Encode(const v2: TInfoV2; var info: TEccCertificateContentV2);
var
  d: PByte;
  max: integer;
begin
  d := @info.Data;
  max := SizeOf(info.Data) - ToVarUInt32LengthWithData(length(v2.Subject));
  if max < 0 then
    exit;
  d := ToVarString(v2.Subject, d);
  info.DataLen := PAnsiChar(d) - PAnsiChar(@info.Data);
end;

procedure TEccCertificateContent.SetSubject(const sub: RawUtf8; maxversion: byte);
var
  iss: TEccCertificateIssuer;
  baudot: RawByteString;
  truncated: boolean;
  v2: TInfov2;
begin
  // EccID(128-bit hexa) can't be stored as V1 because EccText() is then wrong
  // try to store as Baudot - #13/#10 are Baudot-friendly so replace ','/'.'
  truncated := EccIssuer(StringReplaceChars(StringReplaceChars(TrimControlChars(
    sub), ',', #13), '.', #10), iss, @baudot);
  if Head.Version = 1 then
    if truncated and
       (maxversion >= 2) then
    begin
      FillZero(THash128(Head.Signed.Issuer)); // Issuer set to blank in V2
      Head.Version := 2; // we need the new format and its V2 Subject field
    end
    else
    begin
      Head.Signed.Issuer := iss; // (un)truncated content in V1 Issuer
      exit;
    end;
  // Decode(Info, v2); // needed when more than one Subject field is stored
  v2.Subject := baudot;
  Encode(v2, Info);
end;

function TEccCertificateContent.GetSubject: RawUtf8;
var
  v2: TInfov2;
begin
  if Head.Version = 1 then
    result := EccText(Head.Signed.Issuer) // Subject was stored in V1 Issuer
  else
  begin
    Decode(Info, v2);
    result := BaudotToAscii(v2.Subject); // new V2 subject field
  end;
  result := StringReplaceChars(StringReplaceChars(result, #10, '.'), #13, ',');
end;

function TEccCertificateContent.Check: boolean;
begin
  if (Head.Signed.IssueDate = 0) or
     (Head.Signed.IssueDate = 65535) or
     IsZero(Head.Signed.Serial) or
     IsZero(Head.Signed.AuthoritySerial) or
     IsZero(Head.Signed.PublicKey) or
     IsZero(Head.Signature) then
    result := false
  else
    result := (Head.Version in [1, 2]) and
              (ComputeCrc32 = Head.CRC);
end;

function TEccCertificateContent.CheckDate(nowdate: PEccDate;
  TimeUtc: TDateTime): boolean;
var
  now: TEccDate;
begin
  if TimeUtc = 0 then
    now := NowEccDate // default is to check validity against current timestamp
  else
    now := EccDate(TimeUtc);
  if nowdate <> nil then
    nowdate^ := now;
  result := (Head.Signed.IssueDate <= now) and
            ((Head.Signed.ValidityStart = 0) or
             (Head.Signed.ValidityStart <= now)) and
            ((Head.Signed.ValidityEnd = 0) or
             (Head.Signed.ValidityEnd >= now));
end;

function TEccCertificateContent.IsSelfSigned: boolean;
begin
  result := IsEqual(Head.Signed.AuthoritySerial, Head.Signed.Serial) and
            not IsZero(Head.Signed.Serial);
end;

function TEccCertificateContent.FieldsEqual(
  const another: TEccCertificateContent): boolean;
begin
  result := CompareMem(@Head, @another.Head, SizeOf(Head));
  if Head.Version > 1 then
    // compare additional Info content
    result := CompareMem(@Info, @another.Info, Info.DataLen + 4);
end;

procedure TEccCertificateContent.CopyTo(out dest: TEccCertificateContent);
begin
  MoveFast(Head, Dest.Head, SizeOf(Head));
  if Head.Version > 1 then
    // copy additional Info content to the destination
    MoveFast(Info, Dest.Info, Info.DataLen + 4);
end;

function TEccCertificateContent.ComputeCrc32: cardinal;
begin
  result := fnv32(0, @Head, SizeOf(Head) - 4);
  if Head.Version > 1 then
    // include Info content to the CRC
    result := fnv32(result, @Info, Info.DataLen + 4);
end;

procedure TEccCertificateContent.ComputeHash(out hash: TSha256Digest;
  const salt: RawByteString);
var
  sha: TSha256;
begin
  sha.Init;
  sha.Update(salt);
  sha.Update(@Head.Signed, SizeOf(Head.Signed));
  if Head.Version > 1 then
    // include Info content to the SHA-2 digest
    sha.Update(@Info, Info.DataLen + 4);
  sha.Final(hash);
end;

function TEccCertificateContent.SaveToStream(s: TStream): boolean;
begin
  result := s.Write(Head, SizeOf(Head)) = SizeOf(Head);
  if not result then
    exit;
  if Head.Version > 1 then
    // include Info content to the stream
    result := s.Write(Info, Info.DataLen + 4) = Info.DataLen + 4;
end;

function TEccCertificateContent.LoadFromStream(s: TStream; maxversion: byte): boolean;
begin
  result := s.Read(Head, SizeOf(Head)) = SizeOf(Head);
  if not result then
    exit;
  if Head.Version > 1 then
    if Head.Version > maxversion then
       result := false
    else
      // include Info content from the stream
      result := (s.Read(Info, 4) = 4) and
                (Info.DataLen <= SizeOf(Info.Data)) and
                (s.Read(Info.Data, Info.DataLen) = Info.DataLen);
end;


{ TEccSignatureCertifiedContent }

function TEccSignatureCertifiedContent.Check: boolean;
begin
  result := (Version in [1]) and
            (Date <> 0) and
            not IsZero(AuthoritySerial) and
            not IsZero(Signature);
end;

function TEccSignatureCertifiedContent.FromBase64(const base64: RawUtf8): boolean;
begin
  result := Base64ToBin(pointer(base64), @self, length(base64), SizeOf(self));
end;

function TEccSignatureCertifiedContent.ToText: RawUtf8;
begin
  if Check then
    result := BinToBase64(@self, SizeOf(self))
  else
    result := '';
end;

function Ecc256r1DoVerify(const pub: TEccPublicKey; unc: PEccPublicKeyUncompressed;
  const hash: TEccHash; const sign: TEccSignature): boolean;
begin
  if unc = nil then
    result := Ecc256r1Verify(pub, hash, sign)
  else
    result := Ecc256r1VerifyUncomp(unc^, hash, sign);
end;

function TEccSignatureCertifiedContent.Verify(const hash: THash256;
  const auth: TEccCertificateContent; authuncomp: PEccPublicKeyUncompressed;
  TimeUtc: TDateTime): TEccValidity;
var
  now: TEccDate;
begin
  if IsZero(hash) then
    result := ecvBadParameter
  else if not Check then
    result := ecvCorrupted
  else if not auth.Check then
    result := ecvUnknownAuthority
  else if not auth.CheckDate(@now, TimeUtc) then
    result := ecvDeprecatedAuthority
  else if Date > now then
    result := ecvInvalidDate
  else if not Ecc256r1DoVerify(auth.Head.Signed.PublicKey, authuncomp, hash, Signature) then
    result := ecvInvalidSignature
  else if auth.IsSelfSigned then
    result := ecvValidSelfSigned
  else
    result := ecvValidSigned;
end;

function TEccSignatureCertifiedContent.Verify(const hash: THash256;
  const authkey: TEccPublicKey; valid: TEccValidity;
  TimeUtc: TDateTime): TEccValidity;
var
  now: TEccDate;
begin
  if TimeUtc = 0 then
    now := NowEccDate
  else
    now := EccDate(TimeUtc);
  if IsZero(hash) then
    result := ecvBadParameter
  else if not Check then
    result := ecvCorrupted
  else if Date > now then
    result := ecvInvalidDate
  else if not Ecc256r1Verify(authkey, hash, Signature) then
    result := ecvInvalidSignature
  else
    result := valid;
end;


{ TEccCertificateRevocation }

const
  ECC_REVOC_MAGIC = 65535;
  ECC_REVOC_NONE = 7; // = crrNotRevoked = unused reason in RFC5280
  ECC_REVOC_MAX = 10; // see RFC5280

function TEccCertificateRevocation.Check: boolean;
begin
  result := (Magic = ECC_REVOC_MAGIC) and
            (Version in [1]) and
            (Date <> 0) and
            (Reason <> ECC_REVOC_NONE) and // crrNotRevoked is unexpected here
            (Reason <= ECC_REVOC_MAX) and  // RFC5280 defines 11 entries
            not IsZero(Serial);
end;

function TEccCertificateRevocation.FromBase64(const base64: RawUtf8): boolean;
begin
  result := Base64ToBin(base64, @self, SizeOf(self)) and
            Check;
end;

function TEccCertificateRevocation.LoadFromStream(st: TStream): boolean;
begin
  result := (st.Read(self, SizeOf(self)) = SizeOf(self)) and
            Check;
end;

procedure TEccCertificateRevocation.SaveToStream(st: TStream);
begin
  st.WriteBuffer(self, SizeOf(self));
end;

function TEccCertificateRevocation.ToBase64: RawUtf8;
begin
  if Check then
    result := BinToBase64(@self, SizeOf(self))
  else
    result := '';
end;

function TEccCertificateRevocation.From(const id: TEccCertificateID;
  dt: TDateTime; why: word): boolean;
begin
  if (why = ECC_REVOC_NONE) or
     (why > ECC_REVOC_MAX) or
     IsZero(id) then
    result := false
  else
  begin
    Magic := ECC_REVOC_MAGIC;
    Version := 1;
    Date := EccDate(dt);
    Reason := why;
    Serial := id;
    result := Date <> 0;
  end;
end;




initialization
  assert(ECC_QUAD = 4);
  assert(SizeOf(TEccCertificateContentV1) = 173); // on all platforms/compilers
  assert(SizeOf(TEccSignatureCertifiedContent) = 100);
  // register our branchless pascal code by default
  @Ecc256r1MakeKey := @ecc_make_key_pas;
  @Ecc256r1SharedSecret := @ecdh_shared_secret_pas;
  @Ecc256r1Sign := @ecdsa_sign_pas;
  @Ecc256r1Verify := @ecdsa_verify_pas;
  @Ecc256r1Uncompress := @ecc_uncompress_key_pas;
  @Ecc256r1VerifyUncomp := @ecdsa_verify_uncompressed_pas;

end.

